package bdsender

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"

	"github.com/tidwall/gjson"
)

type item struct {
	Type  string  `json:"type,omitempty"`
	Kind  string  `json:"kind,omitempty"`
	Path  string  `json:"path,omitempty"`
	Width int     `json:"width,omitempty"`
	Min   float64 `json:"min,omitempty"`
	Max   float64 `json:"max,omitempty"`
	Scale string  `json:"scale,omitempty"`
	Value string  `json:"value,omitempty"`
}

func widthedBool(width int, val bool) string {
	s := make([]byte, width)
	var b byte
	if val {
		b = '1'
	} else {
		b = '0'
	}
	for idx := range s {
		s[idx] = b
	}
	return string(s)
}

func (it item) encodeBool(r gjson.Result) (string, error) {
	return widthedBool(it.Width, r.Bool()), nil
}

func widthedInt(width int, value uint) string {
	if width <= 0 {
		return ""
	}
	var max uint64 = 2 << uint(width-1)
	if uint64(value) >= max {
		value = uint(max - 1)
	}
	format := fmt.Sprintf("%%0%db", width)
	return fmt.Sprintf(format, value)
}

func (it item) encodeFloat(r gjson.Result) (string, error) {
	switch r.Type.String() {
	case "Number": // nolint
		{
			raw := r.Float()
			min := it.Min
			mag := it.Max - it.Min
			value := uint((raw - min) * math.Pow(2, float64(it.Width)) / mag)
			return widthedInt(it.Width, value), nil
		}
	default:
		raw := it.Max
		min := it.Min
		mag := it.Max - it.Min
		value := uint((raw - min) * math.Pow(2, float64(it.Width)) / mag)
		return widthedInt(it.Width, value), nil
	}
}

func (it item) encodeInt(r gjson.Result) (string, error) {
	switch r.Type.String() {
	case "Number": // nolint
		{
			raw := r.Float()
			min := it.Min
			mag := it.Max - it.Min
			value := uint((raw - min) * math.Pow(2, float64(it.Width)) / mag)
			return widthedInt(it.Width, value), nil
		}
	default:
		raw := it.Max
		min := it.Min
		mag := it.Max - it.Min
		value := uint((raw - min) * math.Pow(2, float64(it.Width)) / mag)
		return widthedInt(it.Width, value), nil
	}
}

func (it item) encodeBytes(r gjson.Result) (string, error) {
	switch r.Type.String() {
	case "String":
		{
			raw := r.String()
			bytes := strings.Split(raw, ",")
			bs := ""
			for _, hex := range bytes {
				val, err := strconv.ParseUint(hex, 0, 8)
				if err != nil {
					return "", err
				}
				b := fmt.Sprintf("%08b", val)
				bs = bs + b
			}
			return bs, nil
		}
	default:
		return "", fmt.Errorf("unmatch data: %v", it)
	}
}

func toGJSON(value string) gjson.Result {
	return gjson.Parse(value)
}

func (it item) Encode(data string) (string, error) {
	var r gjson.Result
	switch it.Kind {
	case "JSON": // nolint
		{
			r = gjson.Get(data, it.Path)
			break
		}
	case "CONST":
		{
			r = toGJSON(it.Value)
			break
		}
	default:
		{
			return "", fmt.Errorf("unknown item kind: %s", it.Kind)
		}
	}
	switch it.Type {
	case "BOOL":
		{
			return it.encodeBool(r)
		}
	case "FLOAT":
		{
			return it.encodeFloat(r)
		}
	case "INT":
		{
			return it.encodeInt(r)
		}
	case "BYTES": // nolint
		{
			return it.encodeBytes(r)
		}
	default:
		{
			return "", errors.New("invalid data")
		}
	}
}
